class Pool {
  constructor(factory) {
    this.buff = [];
    this.factory = factory;
  }
  checkOut() {
    if (this.buff.length === 0) {
      let item = this.factory();
      // console.log('new item from pool: ', item);
      return item;
    } else {
      let item = this.buff.pop();
      // console.log('pop item from pool: ', item);
      return item;
    }
  }
  checkIn(item) {
    // console.log('release item to pool: ', item);
    this.buff.push(item);
  }
  size() {
    return this.buff.length;
  }
}

class Viewer {
  /**
   *
   * @param {HTMLElement} element
   * @param {any[]} data
   */
  constructor(element, data) {
    /**
     * @type {{
     *   src: string;
     *   width: number;
     *   height: number;
     *   top: number;
     *   bottom: number;
     * }[]}
     */
    this.imgs = [];
    this.visibleRange = [0, 0];
    this.imgElementPool = new Pool(() => {
      let el = document.createElement('img');
      el.style.position = 'absolute';
      el.style.visibility = 'hidden';
      element.appendChild(el);
      return el;
    });

    this.element = element;
    this.offsetTop = element.offsetTop;
    this.setData(data);

    element.style.setProperty('height', this.height + 'px');

    this.updateViewport();

    document.addEventListener('scroll', (e) => {
      // console.log('on doucment scroll: ', e);
      this.updateViewport();
    });

    // viewer.addEventListener('scroll', function(e) {
    //   console.log(e);
    //   let scrollTop = e.srcElement.scrollTop;
    //   ...
    // })
  }

  setData(data) {
    let position = 0;
    for (let i = 0; i < data.length; i++) {
      let item = data[i];
      let height = item.height;
      this.imgs[i] = {
        ...item,
        top: position,
        bottom: position + height,
      };
      position += height;
    }
    this.height = position;
  }

  updateViewport() {
    let offsetTop = this.offsetTop;
    let scrollTop = document.scrollingElement.scrollTop;
    let height = window.innerHeight;
    let top = scrollTop - offsetTop;
    let bottom = top + height;
    // console.log({ offsetTop, scrollTop, height, top, bottom });
    let newRange = this.getRangeInViewport({ top, bottom });
    this.setVisibleRange(newRange);
  }

  getRangeInViewport({ top, bottom }) {
    let imgs = this.imgs;
    let index = 0;
    for (; index < imgs.length; index++) {
      let img = imgs[index];
      if (img.bottom > top) {
        break;
      }
    }
    let visibleStart = index;
    let visibleEnd = index;
    for (; index < imgs.length; index++) {
      let img = imgs[index];
      if (img.top > bottom) {
        break;
      }
    }
    visibleEnd = index;
    // console.log({ visibleStart, visibleEnd });
    visibleStart = Math.max(0, visibleStart - 2);
    visibleEnd = Math.min(data.length, visibleEnd + 2);
    // console.log({ visibleStart, visibleEnd });
    return [visibleStart, visibleEnd];
  }

  setVisibleRange(newRange) {
    // console.log('start set visible range: ', newRange);

    var visibleRange = this.visibleRange;

    for (let i = visibleRange[0]; i < visibleRange[1]; i++) {
      if (i < newRange[0] || i >= newRange[1]) {
        this.hideImg(i);
      }
    }

    for (let i = newRange[0]; i < newRange[1]; i++) {
      if (i < visibleRange[0] || i >= visibleRange[1]) {
        this.showImg(i);
      }
    }

    visibleRange[0] = newRange[0];
    visibleRange[1] = newRange[1];

    // console.log('end set visible range: ', newRange);
  }

  hideImg(i) {
    var imgs = this.imgs;
    var imgElementPool = this.imgElementPool;
    // hide img
    let img = imgs[i];
    let imgEl = img.element;
    if (imgEl) {
      imgEl.style.visibility = 'hidden';
      imgElementPool.checkIn(imgEl);
      img.element = null;
    }
  }

  showImg(i) {
    var imgs = this.imgs;
    var imgElementPool = this.imgElementPool;
    // show img
    let img = imgs[i];
    if (img.element == null) {
      let imgEl = imgElementPool.checkOut();
      img.element = imgEl;
      imgEl.src = img.src;
      imgEl.style.width = img.width + 'px';
      imgEl.style.height = img.height + 'px';
      imgEl.style.top = img.top + 'px';
      imgEl.style.left = '0';
      // imgEl.style.visibility = 'visible';
      imgEl.style.removeProperty('visibility');
    }
  }
}

class Rect {
  constructor(x, y, w, h) {
    this.left = x;
    this.top = y;
    this.width = w;
    this.height = h;
  }
}
